using System;
using System.Collections.Generic;
using System.Text;
using CodePlex.Resourceful;

namespace CodePlex.SpaceBlock.FS
{
    public static class F
    {




        public static IEnumerable<TOutput> Inscrutable<TInput, TOutput, TArg>(TArg arg, Fn<TArg, IEnumerable<TInput>> input, params Fn<TInput, TArg, IEnumerable<TOutput>>[] fns)
        {
            return SelectMany(Reduce(input, arg)(), Combine(ReduceAll(fns, arg)));
        }

        public static IEnumerable<TFinal> InscrutableDowncast<TInput, TOutput, TArg,TFinal>(TArg arg, Fn<TArg, IEnumerable<TInput>> input, params Fn<TInput, TArg, IEnumerable<TOutput>>[] fns)
            where TFinal : TOutput
        {
            return DowncastAll<TOutput, TFinal>(Inscrutable<TInput, TOutput, TArg>(arg, input, fns));
        }


        private static IEnumerable<TOutput> SelectMany<TInput, TOutput>(IEnumerable<TInput> input, Fn<TInput, IEnumerable<TOutput>> fn)
        {
            foreach (TInput inputValue in input)
            {
                foreach (TOutput rt in fn(inputValue))
                {
                    yield return rt;
                }
            }
        }



        public static Fn<TInput, IEnumerable<TOutput>> Combine<TInput, TOutput>(IEnumerable<Fn<TInput, IEnumerable<TOutput>>> input)
        {
            return delegate(TInput item)
            {
                return InternalCombine(item, input);
            };

        }
        private static IEnumerable<TOutput> InternalCombine<TInput, TOutput>(TInput input, IEnumerable<Fn<TInput, IEnumerable<TOutput>>> fns)
        {
            foreach (Fn<TInput, IEnumerable<TOutput>> fn in fns)
            {
                foreach (TOutput rt in fn(input))
                {
                    yield return rt;
                }
            }
        }
        


        private static IEnumerable<TOutput> ConvertAll<TInput, TOutput>(IEnumerable<TInput> input, Fn<TInput, TOutput> fn)
        {
            foreach (TInput inputValue in input)
            {
                yield return fn(inputValue);
            }
        }







        public static TOutput Downcast<TInput, TOutput>(TInput input)
            where TOutput : TInput
        {
            return (TOutput)input;
        }

        public static IEnumerable<TOutput> DowncastAll<TInput, TOutput>(IEnumerable<TInput> input)
             where TOutput : TInput
        {
            return ConvertAll<TInput, TOutput>(input, Downcast<TInput, TOutput>);
        }











        // 1 arg -> 0 arg

        public static Fn<TResult> Reduce<T0, TResult>(Fn<T0, TResult> fn, T0 arg)
        {
            return delegate
            {
                return fn(arg);
            };
        }

        public static IEnumerable<Fn<TResult>> ReduceAll<T0, TResult>(IEnumerable<Fn<T0, TResult>> fns, T0 arg)
        {
            return ConvertAll<Fn<T0, TResult>, Fn<TResult>>(fns, delegate(Fn<T0, TResult> item)
            {
                return Reduce(item, arg);
            });
        }


        // 2 args -> 1 arg

        public static Fn<T1, TResult> Reduce<T0, T1, TResult>(Fn<T0, T1, TResult> fn, T0 arg)
        {
            return delegate(T1 item)
            {
                return fn(arg, item);
            };
        }

        public static Fn<T0, TResult> Reduce<T0, T1, TResult>(Fn<T0, T1, TResult> fn, T1 arg)
        {
            return delegate(T0 item)
            {
                return fn(item, arg);
            };
        }



        public static IEnumerable<Fn<T1, TResult>> ReduceAll<T0, T1, TResult>(IEnumerable<Fn<T0, T1, TResult>> fns, T0 arg)
        {
            return ConvertAll<Fn<T0, T1, TResult>, Fn<T1, TResult>>(fns, delegate(Fn<T0, T1, TResult> item)
            {
                return Reduce(item, arg);
            });
        }

        public static IEnumerable<Fn<T0, TResult>> ReduceAll<T0, T1, TResult>(IEnumerable<Fn<T0, T1, TResult>> fns, T1 arg)
        {
            return ConvertAll<Fn<T0, T1, TResult>, Fn<T0, TResult>>(fns, delegate(Fn<T0, T1, TResult> item)
            {
                return Reduce(item, arg);
            });
        }







    }
}
